package com.leyou.auth.service.impl;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.entity.AppInfo;
import com.leyou.auth.entity.ApplicationInfo;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.auth.service.IAuthService;
import com.leyou.gateway.client.UserClient;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.dto.UserDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 处理请求和响应的方法
 *
 * @version V1.0
 * @package com.leyou.auth.service.impl
 * @description:
 * @author: pc
 * @date: 2019/7/10 22:29
 */
@Slf4j
@Service("authService")
public class AuthServiceImpl implements IAuthService {

    @Autowired
    private UserClient userClient;

    @Autowired
    private JwtProperties prop;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private ApplicationInfoMapper infoMapper;

    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public void userLogin(String username, String password, HttpServletResponse response) {
        try {
            //查询用户是否存在
            UserDto user = userClient.queryUserByUsernameAndPassword(username, password);
            if (user == null) {
                throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
            }
            //创建用户的token
            UserInfo userInfo = new UserInfo(user.getId(), user.getUsername(), "GUEST");
            String token = JwtUtils.generateTokenExpireInMinutes(userInfo, prop.getPrivateKey(), prop.getUser().getExpire());
            //将token保存到cookie中
            writeCookie(response, token);
        } catch (LyException e) {
            log.error("用户登录失败，原因：{}", e.getMessage(), e);
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
    }

    private void writeCookie(HttpServletResponse response, String token) {
        CookieUtils.newCookieBuilder()
                .name(prop.getUser().getCookieName())
                .value(token)
                .domain(prop.getUser().getCookieDomain())
                .httpOnly(true)
                .response(response)
                .build();
    }

    @Override
    public UserInfo userVerify(HttpServletRequest request, HttpServletResponse response) {
        //1.从cookie中得到token
        String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());
        try {
            //2.解析token
            Payload<UserInfo> infoFromToken = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
            //获取token的id
            String id = infoFromToken.getId();
            //在黑名单中检索是否存在
            Boolean aBoolean = redisTemplate.hasKey(id);
            if (aBoolean != null && aBoolean) {
                //存在于黑名单则抛出异常
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }
            //3.token的续期
            //3.1取得token的过期时间
            Date expiration = infoFromToken.getExpiration();
            //3.2判断token的有效时间
            long ramainTime = expiration.getTime() - System.currentTimeMillis();
            if (ramainTime < prop.getUser().getMinRemainTime()) {
                //3.3过期的话重新生成token
                String newToken = JwtUtils.generateTokenExpireInMinutes(infoFromToken.getUserInfo(), prop.getPrivateKey(), prop.getUser().getExpire());
                //3.4写入cookie中
                writeCookie(response, token);
            }
            //返回token
            return infoFromToken.getUserInfo();
        } catch (Exception e) {
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }


    @Override
    public void userLogout(HttpServletRequest request, HttpServletResponse response) {
        //1.从cookie中得到token
        String token = CookieUtils.getCookieValue(request, prop.getUser().getCookieName());
        //2.获取token
        Payload<UserInfo> infoFromToken = JwtUtils.getInfoFromToken(token, prop.getPublicKey(), UserInfo.class);
        //3.1取得token的过期时间
        Date expiration = infoFromToken.getExpiration();
        //3.2取得token的id值
        String id = infoFromToken.getId();
        //3.3获得token的有效时间
        long ramainTime = expiration.getTime() - System.currentTimeMillis();
        //存入Redis黑名单中
        // 写入redis, 剩余时间超过5秒以上才写
        if (ramainTime > 5000) {
            redisTemplate.opsForValue().set(id, "", ramainTime, TimeUnit.MILLISECONDS);
        }
        CookieUtils.deleteCookie(prop.getUser().getCookieName(),prop.getUser().getCookieDomain(),response);
    }


    @Override
    public String authenticate(Long id, String secret) {
        try {
            // 根据id查询应用信息
            ApplicationInfo app = infoMapper.selectByPrimaryKey(id);
            // 判断是否为空
            if (app == null) {
                // id不存在，抛出异常
                throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
            }
            // 校验密码
            if (!passwordEncoder.matches(secret, app.getSecret())) {
                // 密码错误
                throw new LyException(ExceptionEnum.INVALID_SERVER_ID_SECRET);
            }
            // 查询app的权限信息
            List<Long> idList = infoMapper.queryTargetIdList(id);
            // 封装AppInfo
            AppInfo appInfo = new AppInfo();
            appInfo.setId(id);
            appInfo.setServiceName(app.getServiceName());
            appInfo.setTargetList(idList);
            // 生成JWT并返回
            return JwtUtils.generateTokenExpireInMinutes(
                    appInfo, prop.getPrivateKey(), prop.getApp().getExpire());
        } catch (LyException e) {
            log.error("服务授权认证失败，原因：{}",e.getMessage(),e);
            throw new LyException(ExceptionEnum.UNAUTHORIZED,e);
        }
    }
}
